<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body style="font: 14px sans-serif">
<h1>HNTrieContainer test</h1>
<p><a href="./.">Back</a></p>
<p>&nbsp;</p>
<div><button id="test" type="button">Test!</button></div>
<div id="stdout"></div>
<script src="https://rawcdn.githack.com/gorhill/uBlock/1b6fea16da81d1df3e2efd5a31894f71ea04dbb1/src/js/hntrie.js"></script>
<!-- <script src="../../src/js/hntrie.js"></script> -->
<script src="hostname-pool.js"></script>
<script>
const createRandomLabel = function() {
    return Math.random().toString(36).slice(2);
};

const stdout = function(s) {
    const line = document.createElement('div');
    const parent = document.getElementById('stdout');
    let tooManyErrors = false;
    if ( parent.childElementCount > 100 ) {
        tooManyErrors = true;
        line.textContent = 'Too many errors, aborting';
    } else {
        line.textContent = s;
    }
    parent.appendChild(line)
    if ( tooManyErrors ) {
        throw 'Aborting';
    }
}

/******************************************************************************/

// Dictionary of hostnames
//
const FilterHostnameDict = function(hostnames) {
    this.h = ''; // short-lived register
    this.dict = new Set();
    if ( hostnames !== undefined ) {
        this.fromIterable(hostnames);
    }
};

FilterHostnameDict.prototype = {
    add: function(hn) {
        if ( this.dict.has(hn) ) { return false; }
        this.dict.add(hn);
        return true;
    },
    fromIterable: function(hostnames) {
        for ( let hn of hostnames ) {
            this.add(hn);
        }
        return this;
    },
    matches: function(needle) {
        while ( this.dict.has(needle) === false ) {
            const pos = needle.indexOf('.');
            if ( pos === -1 ) {
                this.h = '';
                return false;
            }
            needle = needle.slice(pos + 1);
        }
        this.h = needle;
        return true;
    },
};

/******************************************************************************/

const testFlavor = function(hostnames, name, matchesFn, hitFn) {
    stdout('\xA0');
    stdout('Testing ' + name + '...');

    const t0 = performance.now();

    for ( let i = 0; i < hostnames.length; i++ ) {
        // Exact hits
        let needle = hostnames[i];
        if ( hitFn(matchesFn(needle)) === false ) {
            stdout('Exact hits failed: ' + needle);
        }

        // Subdomain hits
        needle = createRandomLabel() + '.' + hostnames[i];
        if ( hitFn(matchesFn(needle)) === false ) {
            stdout('Subdomain hits failed: ' + needle);
        }

        // Misses batch 1
        needle = createRandomLabel() + '.com';
        if ( hitFn(matchesFn(needle)) !== false ) {
            stdout('Misses batch 1: ' + needle);
        }

        // Misses batch 2
        needle = hostnames[i] + '.' + createRandomLabel();
        if ( hitFn(matchesFn(needle)) !== false ) {
            stdout('Misses batch 2: ' + needle);
        }

        // Misses batch 3
        needle = hostnames[i];
        let pos = needle.lastIndexOf('.');
        if ( pos !== -1 ) {
            needle = needle.slice(0, pos) + needle.slice(pos + 1);
            if ( hitFn(matchesFn(needle)) !== false ) {
                stdout('Misses batch 3: ' + needle);
            }
        }
    }

    const t1 = performance.now();

    stdout(
        name + ': ' +
        (hostnames.length * 5).toLocaleString() +
        ' tests completed in ' +
        (t1 - t0).toFixed(2) + ' ms'
    );
};

const hnBigTrieJS = new HNTrieContainer();
const hnBigTrieWASM = new HNTrieContainer();
const hnBigTrieUnserialized = new HNTrieContainer();

Promise.all([
    hnBigTrieJS.readyToUse(),
    hnBigTrieWASM.readyToUse()
]).then(( ) => {
    let t0 = performance.now();
    const theSet = new FilterHostnameDict(hostnamePool);
    let t1 = performance.now();
    stdout('\xA0');
    stdout(
        'Set creation completed in ' +
        (t1 - t0).toFixed(2) + ' ms'
    );

    t0 = performance.now();
    const theTrieJS = hnBigTrieJS.fromIterable(hostnamePool, 'addJS');
    hnBigTrieJS.optimize();
    t1 = performance.now();
    stdout('\xA0');
    stdout(
        'HNTrieContainer creation (JS) completed in ' +
        (t1 - t0).toFixed(2) + ' ms'
    );

    let theTrieWASM;
    if ( hnBigTrieWASM.addWASM instanceof Function ) {
        t0 = performance.now();
        theTrieWASM = hnBigTrieWASM.fromIterable(hostnamePool, 'addWASM');
        hnBigTrieWASM.optimize();
        t1 = performance.now();
        stdout('\xA0');
        stdout(
            'HNTrieContainer creation (WASM) completed in ' +
            (t1 - t0).toFixed(2) + ' ms'
        );

        const bufJS = theTrieJS.container.buf;
        const bufWASM = theTrieWASM.container.buf;
        for ( let i = 0; i < bufJS.length; i++ ) {
            if ( bufJS[i] !== bufWASM[i] ) {
                stdout('theTrieWASM failure at index ' + i);
                break;
            }
        }
    }

    let selfie = hnBigTrieJS.serialize();
    t0 = performance.now();
    hnBigTrieUnserialized.unserialize(selfie);
    const theTrieUnserialized = hnBigTrieUnserialized.createOne(hnBigTrieJS.compileOne(theTrieJS));
    t1 = performance.now();
    stdout('\xA0');
    stdout(
        'HNTrieContainer creation (unserialized) completed in ' +
        (t1 - t0).toFixed(2) + ' ms'
    );
    selfie = undefined;

    document.getElementById('test').addEventListener('click', ( ) => {
        let parent = document.getElementById('stdout');
        while ( parent.childElementCount !== 0 ) {
            parent.removeChild(parent.firstChild);
        }
        testFlavor(
            hostnamePool,
            'Set (JS)',
            theSet.matches.bind(theSet),
            r => r 
        );
        testFlavor(
            hostnamePool,
            'HNTrieContainer (JS)',
            theTrieJS.matchesJS.bind(theTrieJS),
            r => r >= 0
        );
        if ( theTrieWASM !== undefined ) {
            testFlavor(
                hostnamePool,
                'HNTrieContainer (WASM)',
                theTrieWASM.matchesWASM.bind(theTrieWASM),
                r => r >= 0
            );
        }
        testFlavor(
            hostnamePool,
            'HNTrieContainer (unserialized)',
            theTrieUnserialized.matchesJS.bind(theTrieUnserialized),
            r => r >= 0
        );
    });
});

</script>
</body>
</html>
